/**
 * Text Utilities for Programmer's Notepad
 */

#include "stdafx.h"
#include "plugin.h"
#include "wrapscintilla.h"
#include "menu.h"

static const int INDIC_MARKALL = INDIC_CONTAINER;
static const int INDIC_SMARTHIGHLIGHT = INDIC_CONTAINER + 1;
static const int INDIC_OVERWRITETARGET = INDIC_CONTAINER + 2;

using namespace extensions;

void SortLines(int cookie);
void CopyMarkedLines(int cookie);
void CutMarkedLines(int cookie);
void BookmarkMarkedLines(int cookie);
void MarkedRangesToSelections(int cookie);
void SelectionsToMarkedRanges(int cookie);
void SmartHighlightToSelections(int cookie);
void SmartHighlightToMarkedRanges(int cookie);
void ReverseSelections(int cookie);

/**
 * Build our menu for PN to display.
 */
extensions::IMenuItems* MakeCommands()
{
	Menu* menu = new Menu();

	MenuItem sep = { extensions::miItem, L"-", 0, NULL, NULL };
	MenuItem item1 = { extensions::miItem, L"Sort Lines", 0, SortLines, NULL };	
	MenuItem item2 = { extensions::miItem, L"Copy Marked Lines", 0, CopyMarkedLines, NULL };
	MenuItem item3 = { extensions::miItem, L"Cut Marked Lines", 0, CutMarkedLines, NULL };
	MenuItem item4 = { extensions::miItem, L"Bookmark Marked Lines", 0, BookmarkMarkedLines, NULL };
	MenuItem item5 = { extensions::miItem, L"Marked Ranges to Selections", 0, MarkedRangesToSelections, NULL };
	MenuItem item6 = { extensions::miItem, L"Selections to Marked Ranges", 0, SelectionsToMarkedRanges, NULL };
	MenuItem item7 = { extensions::miItem, L"Smart Highlight to Selections", 0, SmartHighlightToSelections, NULL };
	MenuItem item8 = { extensions::miItem, L"Smart Highlight to Marked Ranges", 0, SmartHighlightToMarkedRanges, NULL };
	MenuItem item9 = { extensions::miItem, L"Reverse Selection(s)", 0, ReverseSelections, NULL };

	menu->AddItem(item1);
	menu->AddItem(sep);
	menu->AddItem(item2);
	menu->AddItem(item3);
	menu->AddItem(sep);
	menu->AddItem(item4);
	menu->AddItem(item5);
	menu->AddItem(item6);
	menu->AddItem(item7);
	menu->AddItem(item8);
	menu->AddItem(sep);
	menu->AddItem(item9);
	
	return menu;
}

/**
 * Helper function to set the current clipboard text.
 */
void PNSetClipboardText(const char* text)
{
	if (text == NULL)
	{
		return;
	}

	size_t length = strlen(text);

	HGLOBAL hData = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, length + 1);
	if (hData != NULL)
	{
		HWND owner = g_Context.PN->GetMainWindow();

		if (::OpenClipboard(owner))
		{
			::EmptyClipboard();
			
			char* pBuf = static_cast<char*>(::GlobalLock(hData));
			memcpy(pBuf, &text[0], length + 1);
			::GlobalUnlock(hData);
			
			::SetClipboardData(CF_TEXT, hData);
			::CloseClipboard();
		}
	}
}

/**
 * Initial naive sort implementation
 */
void SortLines(int cookie)
{
	IDocumentPtr doc = g_Context.PN->GetCurrentDocument();
	PNScintilla scintilla(doc->GetScintillaHWND());

	int startSel = scintilla.LineFromPosition(scintilla.GetSelectionStart());
	int endSel = scintilla.LineFromPosition(scintilla.GetSelectionEnd());

	std::vector<std::string> lines;
	scintilla.GetLines(startSel, endSel, lines);

	std::sort(lines.begin(), lines.end());

	int startPos = scintilla.PositionFromLine(startSel);

	// Remove current selected text:
	scintilla.SetTarget(startPos, scintilla.GetLineEndPosition(endSel));
	scintilla.ReplaceTarget(0, "");

	// Set lines:
	scintilla.InsertLines(startPos, lines);
}

/**
 * Get the position of the first indicator
 */
int GetFirstIndicator(PNScintilla& scintilla, int indicatorType)
{
	// Find the range of the indicator, we start by finding the end because it's the first
	// end we'll find in the document.
	bool onAt0 = scintilla.SPerform(SCI_INDICATORVALUEAT, indicatorType, 0) != 0;
	
	return onAt0 ? 0 : scintilla.SPerform(SCI_INDICATOREND, indicatorType, 0);
}

/**
 * Copy the marked lines in the current editor to the clipboard, optionally deleting
 * the contents as we go.
 */
void CopyMarkedLines(bool deleteAfterCopy)
{
	IDocumentPtr doc = g_Context.PN->GetCurrentDocument();
	PNScintilla scintilla(doc->GetScintillaHWND());

	// Find the first indicator:
	int start = GetFirstIndicator(scintilla, INDIC_MARKALL);
	
	if (start == scintilla.GetLength())
	{
		// No marked indicators:
		return;
	}

	int end = scintilla.SPerform(SCI_INDICATOREND, INDIC_MARKALL, start);

	if (start == end)
	{
		// No marked ranges:
		return;
	}

	int original = start;
	int last = start;
	std::string buffer;

	do
	{
		int curLine = scintilla.LineFromPosition(start);
		int endLine = scintilla.LineFromPosition(end);

		while(curLine <= endLine)
		{
			buffer += scintilla.GetLineAsString(curLine);
			curLine++;
		}

		if (deleteAfterCopy)
		{
			int startDelete = scintilla.PositionFromLine(scintilla.LineFromPosition(start));
			int endDelete = (endLine == scintilla.GetLineCount()) ? 
				scintilla.GetLineEndPosition(endLine) : 
				scintilla.PositionFromLine(endLine+1);
			scintilla.SetTarget(startDelete, endDelete);
			scintilla.ReplaceTarget(0, "");
			int length = endDelete - startDelete;
			start -= length;
			end -= length;
		}

		last = start;
		start = scintilla.SPerform(SCI_INDICATOREND, INDIC_MARKALL, end + 1);
		end = scintilla.SPerform(SCI_INDICATOREND, INDIC_MARKALL, start);
	}
	while (start > original && start < end && last != start);

	PNSetClipboardText(buffer.c_str());
}

/**
 * Initial naive sort implementation
 */
void CopyMarkedLines(int cookie)
{
	CopyMarkedLines(false);
}

/**
 * Initial naive sort implementation
 */
void CutMarkedLines(int cookie)
{
	CopyMarkedLines(true);
}

/**
 * Add simple bookmarks on any marked lines.
 */
void BookmarkMarkedLines(int cookie)
{
	IDocumentPtr doc = g_Context.PN->GetCurrentDocument();
	PNScintilla scintilla(doc->GetScintillaHWND());

	// Find the first indicator:
	int start = GetFirstIndicator(scintilla, INDIC_MARKALL);
	
	if (start == scintilla.GetLength())
	{
		// No marked indicators:
		return;
	}

	int end = scintilla.SPerform(SCI_INDICATOREND, INDIC_MARKALL, start);

	if (start == end)
	{
		// No marked ranges:
		return;
	}

	int original = start;
	int last = start;

	do
	{
		int curLine = scintilla.LineFromPosition(start);
		int endLine = scintilla.LineFromPosition(end);

		while(curLine <= endLine)
		{
			if (!(scintilla.MarkerGet(curLine) & (1 << SC_BOOKMARK)))
			{
				scintilla.MarkerAdd(curLine, SC_BOOKMARK);
			}

			curLine++;
		}

		last = start;
		start = scintilla.SPerform(SCI_INDICATOREND, INDIC_MARKALL, end + 1);
		end = scintilla.SPerform(SCI_INDICATOREND, INDIC_MARKALL, start);
	}
	while (start > original && start < end && last != start);
}

/**
 * Create multiple selections over indicators.
 * @param indicator Indicator type.
 */
void ConvertIndicatorsToSelections(int indicator)
{
	IDocumentPtr doc = g_Context.PN->GetCurrentDocument();
	PNScintilla scintilla(doc->GetScintillaHWND());

	// Find the first indicator:
	int start = GetFirstIndicator(scintilla, indicator);
	
	if (start == scintilla.GetLength())
	{
		// No marked indicators:
		return;
	}

	int end = scintilla.SPerform(SCI_INDICATOREND, indicator, start);

	if (start == end)
	{
		// No marked ranges:
		return;
	}

	int original = start;
	int last = start;

	do
	{
		scintilla.AddSelection(end, start);

		last = start;
		start = scintilla.SPerform(SCI_INDICATOREND, indicator, end + 1);
		end = scintilla.SPerform(SCI_INDICATOREND, indicator, start);
	}
	while (start > original && start < end && last != start);
}

/**
 * Create a multiple selection everywhere there's a MarkAll range.
 */
void MarkedRangesToSelections(int cookie)
{
	ConvertIndicatorsToSelections(INDIC_MARKALL);
}

/**
 * Create a multiple selection everywhere there's a smart highlight range.
 */
void SmartHighlightToSelections(int cookie)
{
	ConvertIndicatorsToSelections(INDIC_SMARTHIGHLIGHT);
}

/**
 * Convert smart highlight ranges to mark all ranges.
 */
void SmartHighlightToMarkedRanges(int cookie)
{
	IDocumentPtr doc = g_Context.PN->GetCurrentDocument();
	PNScintilla scintilla(doc->GetScintillaHWND());

	// Find the first INDIC_SMARTHIGHLIGHT:
	int start = GetFirstIndicator(scintilla, INDIC_SMARTHIGHLIGHT);
	
	if (start == scintilla.GetLength())
	{
		// No marked indicators:
		return;
	}

	int end = scintilla.SPerform(SCI_INDICATOREND, INDIC_SMARTHIGHLIGHT, start);

	if (start == end)
	{
		// No marked ranges:
		return;
	}

	int original = start;
	int last = start;

	// Clear all current MarkAll indicators:
	scintilla.SetIndicatorCurrent(INDIC_MARKALL);
	scintilla.IndicatorClearRange(0, scintilla.GetLength());

	// Set that we're going to make rounded box MarkAll indicators:
	scintilla.SetIndicatorValue(INDIC_ROUNDBOX);
	scintilla.IndicSetStyle(INDIC_MARKALL, INDIC_ROUNDBOX);

	do
	{
		scintilla.IndicatorFillRange(start, end-start);

		last = start;
		start = scintilla.SPerform(SCI_INDICATOREND, INDIC_SMARTHIGHLIGHT, end + 1);
		end = scintilla.SPerform(SCI_INDICATOREND, INDIC_SMARTHIGHLIGHT, start);
	}
	while (start > original && start < end && last != start);
}

/**
 * Convert the user selections to MarkAll ranges.
 */
void SelectionsToMarkedRanges(int cookie)
{
	IDocumentPtr doc = g_Context.PN->GetCurrentDocument();
	PNScintilla scintilla(doc->GetScintillaHWND());

	// Clear all current MarkAll indicators:
	scintilla.SetIndicatorCurrent(INDIC_MARKALL);
	scintilla.IndicatorClearRange(0, scintilla.GetLength());

	// Set that we're going to make rounded box MarkAll indicators:
	scintilla.SetIndicatorValue(INDIC_ROUNDBOX);
	scintilla.IndicSetStyle(INDIC_MARKALL, INDIC_ROUNDBOX);

	int selections = scintilla.GetSelections();
	for (int i = 0; i < selections; i++)
	{
		int start = scintilla.GetSelectionNStart(i);
		int end = scintilla.GetSelectionNEnd(i);

		scintilla.IndicatorFillRange(start, end-start);
	}
}

/**
 * Swap the caret and anchor points of each selection
 */
void ReverseSelections(int cookie)
{
	IDocumentPtr doc = g_Context.PN->GetCurrentDocument();
	PNScintilla scintilla(doc->GetScintillaHWND());

	// Clear all current MarkAll indicators:
	scintilla.SetIndicatorCurrent(INDIC_MARKALL);
	scintilla.IndicatorClearRange(0, scintilla.GetLength());

	// Set that we're going to make rounded box MarkAll indicators:
	scintilla.SetIndicatorValue(INDIC_ROUNDBOX);
	scintilla.IndicSetStyle(INDIC_MARKALL, INDIC_ROUNDBOX);

	int selections = scintilla.GetSelections();
	for (int i = 0; i < selections; i++)
	{
		int anchor = scintilla.GetSelectionNAnchor(i);
		int caret = scintilla.GetSelectionNCaret(i);
		
		scintilla.SetSelectionNCaret(i, anchor);
		scintilla.SetSelectionNAnchor(i, caret);
	}
}